/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifndef PAGE_CACHE_MANAGER_HPP
#define PAGE_CACHE_MANAGER_HPP

#include <string>
#include <map>
#include <list>
#include <stdexcept>

#include "Utils/Area.hpp"

struct Page;

// All pagaes are in map: key is page number, data - page pointer
// if style changes, page rotatates then pages should be re calculated
// all renedere pages also stored in list. Its size is limitted its max 
// value passed to constructor.
class PageCacheManager
{
    friend struct Page;
    class PageReleaser;
    
private: //Types
    typedef std::list<Page*> RENDERED;

    typedef std::map<size_t, Page*> PAGE_CACHE;

public:
    PageCacheManager(size_t cache_size)
        : r_pages(), cache(), 
          current_page(0), current_number(0),
          max_size(cache_size)
    {}

    ~PageCacheManager();

    size_t maxCacheSize() const
    {
        return max_size;
    }
    size_t availableCacheSize() const
    {
        return max_size - r_pages.size();
    }
    size_t pageCount() const
    {
        return cache.size();
    }

    bool isPageAvailable(size_t num);

    // Return page from cache or 0 if not found
    Page* getCachedPage(size_t num);

    // Forces PageCacheManager to create new page even it 
    // already exists
    Page* createNewPage(size_t num);

    // Gets page: from cache or create new one
    Page* getPage(size_t num);

    Page* getLastPage();
    Page* getFirstPage();

    // Mark page as unused, should be called after Page was displayed 
    void releasePage(Page* page);

    void destroyPages();

protected:
    void decreaseCache();

    void storeRendered(Page* page);


private:
    RENDERED   r_pages;
    PAGE_CACHE cache;

    Page*      current_page; 
    size_t     current_number;

    size_t     max_size;
};

struct Page
{
    // :TODO: Margins & Borders
    DrawingArea*    area;

    PageOrientation orient;
    
    size_t          num;      // !< Page number
    size_t          start_pos;// !< Start text file position
    size_t          end_pos;  // !< End text file position

    std::string     title;


    Page(size_t n, PageOrientation o = Portrait)
        : area(0), orient(o), num(n),
          start_pos(0), end_pos(0),
          title("")
    {}

    bool isValid()
    {
        return start_pos != end_pos;
    }

    bool isRendered() const
    {
        return (area)? area->buffer != 0 : false;
    }

    DrawingArea* getArea()
    {
        return (area)? area : area = new DrawingArea;
    }

private:
    // Only PageCacheManager could destroy pages
    ~Page()
    {
        if (area)
            delete area;
    }
    
    friend class PageCacheManager;
    friend class PageCacheManager::PageReleaser;
private:
    // disable copying
    Page(const Page&);
    Page& operator=(const Page&);
};

class PageCacheManager::PageReleaser
{
public:
    PageReleaser(Page* _p)
        : p(_p)
    {}

    ~PageReleaser()
    {
        if (p)
            delete p;
    }

    Page* get() const
    {
        return p;
    }

    Page* release()
    {
        Page* _p = p;
        p = 0;

        return _p;
    }
private:
    Page* p;

private:
    PageReleaser(const PageReleaser&);
    PageReleaser& operator=(PageReleaser&);
        
};


class PageSanity
{
private:
    Page* page;

    PageCacheManager& man;
public:
    PageSanity(PageCacheManager& m, Page* p)
        : page(p), man(m)
    {}
    ~PageSanity()
    {
        if (page)
        {
            man.releasePage(page);
        }
    }

    Page* get() const
    {
        return page;
    }

    void release()
    {
        man.releasePage(page);
        page = 0;
    }

    Page* reset(Page* p = 0)
    {
        Page* old = page;
        page = p;

        return old;
    }
    Page* operator->() const
    {
        return page;
    }
    Page& operator*() const
    {
        if (page == 0)
        {
            throw std::invalid_argument("PageSanity: Bad page");
        }
        return *page;
    }
private:
    PageSanity(const PageSanity&);
    PageSanity& operator=(const PageSanity&);
};

#endif //PAGE_CACHE_MANAGER_HPP
